package cbfg.http

import cbfg.http.constant.PostType
import cbfg.http.extra.MapTypeAdapter
import cbfg.http.util.Logger
import cbfg.http.util.PostBodyParser
import cbfg.http.util.Utils
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.util.concurrent.ConcurrentHashMap

/**
 * 网络请求工具类
 */
object HttpManager {
    private val serviceMethodCache: MutableMap<Method, ServiceMethod<*>> = ConcurrentHashMap()
    private val serviceMap: MutableMap<Class<out Any>, Any> = HashMap(8)
    private var hostMap: MutableMap<String, String>? = null
    private var baseUrl: String? = null

    private lateinit var okHttpClient: OkHttpClient
    internal lateinit var gson: Gson
    internal lateinit var converterFactory: ConverterFactory

    fun init(
        okHttpClient: OkHttpClient,
        converterFactory: ConverterFactory? = null,
        gson: Gson? = null
    ): HttpManager {
        this.okHttpClient = okHttpClient

        this.converterFactory = converterFactory ?: object : ConverterFactory {
            override fun convertRequest(
                hostTag: String?,
                httpTag: String?,
                request: Request
            ): Request = request

            override fun convertResponse(
                hostTag: String?,
                httpTag: String?,
                response: String
            ): String = response

            override fun convertRequestBody(
                hostTag: String?,
                httpTag: String?,
                postType: PostType,
                params: Map<String, Any>
            ): RequestBody {
                return PostBodyParser.parse(postType, params)
            }
        }

        this.gson = gson ?: GsonBuilder()
            .registerTypeAdapter(
                object : TypeToken<Map<String, Any?>>() {}.type,
                MapTypeAdapter()
            )
            .create()

        return this
    }

    fun setLogEnabled(enabled: Boolean, tag: String? = null): HttpManager {
        Logger.init(enabled, tag)
        return this
    }

    fun setBaseUrl(url: String): HttpManager {
        this.baseUrl = url
        return this
    }

    fun getBaseUrl() = baseUrl

    fun putHost(hostTag: String, hostUrl: String): HttpManager {
        val map = hostMap ?: HashMap<String, String>(4).also { hostMap = it }
        map[hostTag] = hostUrl
        return this
    }

    fun getHostUrl(hostTag: String): String? {
        return hostMap?.get(hostTag)
    }

    fun getOkHttpClient() = okHttpClient

    fun <T : Any> getService(clazz: Class<T>): T {
        val objService = serviceMap[clazz]
        if (objService != null) {
            return objService as T
        }
        return createService(clazz).also { serviceMap[clazz] = it }
    }

    /**
     * 使用代理模式，生成传入的service类
     *
     * @param service
     * @param <T>
     * @return
    </T> */
    private fun <T> createService(service: Class<T>): T {
        Utils.validateServiceInterface(service)
        return Proxy.newProxyInstance(service.classLoader,
            arrayOf<Class<*>>(service),
            object : InvocationHandler {
                @Throws(Throwable::class)
                override fun invoke(
                    proxy: Any,
                    method: Method,
                    args: Array<Any?>?
                ): Any? {
                    return if (method.declaringClass == Any::class.java) {
                        method.invoke(this, args)
                    } else loadServiceMethod(method).invoke(args)
                }
            }) as T
    }

    /**
     * 获取一个服务方法, 如果该方法已声明，则直接调用
     *
     * @return
     */
    private fun loadServiceMethod(method: Method): ServiceMethod<*> {
        serviceMethodCache[method]?.run { return this }

        synchronized(serviceMethodCache) {
            return serviceMethodCache[method]
                ?: ServiceMethod<Any?>(method).also { serviceMethodCache[method] = it }
        }
    }
}